package org.realogic.cfm.persistence.mybatis.interceptor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.realogic.cfm.persistence.query.QueryCondition;
import org.realogic.cfm.persistence.util.Reflections;

/**
 * <p>
 * Mybatis数据库分页插件. 拦截StatementHandler的prepare方法
 * </p>
 * 
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class PreparePaginationInterceptor extends BaseInterceptor {

	private static final long serialVersionUID = -9204310448706615861L;

	private Log log = LogFactory.getLog(this.getClass());

	public PreparePaginationInterceptor() {
		super();
	}

	@Override
	public Object intercept(Invocation ivk) throws Throwable {
		if (ivk.getTarget().getClass().isAssignableFrom(RoutingStatementHandler.class)) {
			final RoutingStatementHandler statementHandler = (RoutingStatementHandler) ivk.getTarget();
			final BaseStatementHandler delegate = (BaseStatementHandler) Reflections.getFieldValue(statementHandler, DELEGATE);
			final MappedStatement mappedStatement = (MappedStatement) Reflections.getFieldValue(delegate, MAPPED_STATEMENT);

			BoundSql boundSql = delegate.getBoundSql();
			// 参数
			Object parameterObject = boundSql.getParameterObject();
			// Mybatis配置文件id
			//String sqlKey = mappedStatement.getId();
			//System.out.println("sqlKey=" + sqlKey);
			// 拦截QueryCondition类型参数,并判断是否需要分页
			if (parameterObject != null && parameterObject instanceof QueryCondition) {
				final QueryCondition queryCondition = (QueryCondition) parameterObject;
				final String sqlOriginal = boundSql.getSql();
				// 计算总记录数
				if (queryCondition.isDisplayByPage()) {
					// 获取数据库连接
					final Connection connection = (Connection) ivk.getArgs()[0];
					totalSizeOfQuery(queryCondition, mappedStatement, connection);
				}
				// 构造新的SQL
				String sqlNewOrPage = null;
				if (queryCondition.isDisplayByPage()) {
					sqlNewOrPage = SQLHelp.generatePageSql(sqlOriginal, queryCondition, DIALECT);
				} else {
					sqlNewOrPage = SQLHelp.generateQuerySql(sqlOriginal, queryCondition);
				}
				if (log.isDebugEnabled()) {
					log.debug("重构后按条件生成新SQL或分页SQL:" + sqlNewOrPage);
				}
				List<ParameterMapping> newParameterMappings = DIALECT.newParameterMappings(mappedStatement, boundSql, queryCondition, true);
				// 将【新的参数值】反射回BoundSql.
				Reflections.setFieldValue(boundSql, "parameterMappings", newParameterMappings);
				// 将【按条件生成新SQL或分页SQL】语句反射回BoundSql.
				Reflections.setFieldValue(boundSql, "sql", sqlNewOrPage);

			}
		}
		return ivk.proceed();
	}

	@Override
	public Object plugin(Object o) {
		return Plugin.wrap(o, this);
	}

	@Override
	public void setProperties(Properties properties) {
		initProperties(properties);
	}

	private void totalSizeOfQuery(QueryCondition queryCondition, MappedStatement mappedStatement, Connection connection) {
		BoundSql boundSql = mappedStatement.getBoundSql(queryCondition);
		// 获取到我们自己写在Mapper映射语句中对应的Sql语句
		String sqlOriginal = boundSql.getSql();
		// 通过查询Sql语句获取到对应的计算总记录数的sql语句
		String sqlCount = SQLHelp.generateCountSql(sqlOriginal, queryCondition, DIALECT);
		// 通过BoundSql获取对应的参数映射
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		// 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
		Configuration configuration = mappedStatement.getConfiguration();
		BoundSql countBoundSql = new BoundSql(configuration, sqlCount, parameterMappings, queryCondition);
		List<ParameterMapping> newParameterMappings = DIALECT.newParameterMappings(mappedStatement, countBoundSql, queryCondition, false);
		Reflections.setFieldValue(countBoundSql, "parameterMappings", newParameterMappings);
		// 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, queryCondition, countBoundSql);
		// 通过connection建立一个countSql对应的PreparedStatement对象。
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = connection.prepareStatement(sqlCount);
			// 通过parameterHandler给PreparedStatement对象设置参数
			parameterHandler.setParameters(pstmt);
			// 之后就是执行获取总记录数的Sql语句和获取结果了。
			rs = pstmt.executeQuery();
			if (rs.next()) {
				int totalRecord = rs.getInt(1);
				// 给当前的参数page对象设置总记录数
				queryCondition.setTotalSize(totalRecord);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			try {
				if (rs != null)
					rs.close();
				if (pstmt != null)
					pstmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
}
